// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PerformanceCounterManager.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.PerformanceCounters
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Security;
    using Microsoft.Robotics.Tracing;

    /// <summary>
    /// Provides methods for creating and updating the performance counters
    /// </summary>
    public static class PerformanceCounterManager
    {
        /// <summary>
        /// The counter categories we know about
        /// </summary>
        private static Dictionary<string, Dictionary<int, CounterCreationData>> categories = new Dictionary<string, Dictionary<int, CounterCreationData>>();

        /// <summary>
        /// The runtime counters created in the process
        /// </summary>
        private static Dictionary<string, Dictionary<string, Dictionary<int, PerformanceCounter>>> counters = new Dictionary<string, Dictionary<string, Dictionary<int, PerformanceCounter>>>();

        /// <summary>
        /// The process name used to name the counter instances so that multiple processes can each have their own counters
        /// </summary>
        private static string processName;

        /// <summary>
        /// Installs the performance counters
        /// </summary>
        /// <param name="installCounters">If true, the counters are recreated (requires admin permissions)</param>
        public static void SetupCounters(bool installCounters)
        {
            // to make debugging easier, only append the process ID if there is more than one process with the same name
            processName = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcessesByName(processName).Length != 1)
            {
                processName = processName + Process.GetCurrentProcess().Id;
            }

            // set up all the counters we know about - this should eventually be text/xml file-based instead of hardcoded
            RuntimeCounterManager.Setup(installCounters);
            ParticleFilterCounterManager.Setup(installCounters);
        }

        /// <summary>
        /// Creates all the counters for the specified instance.
        /// </summary>
        /// <param name="categoryName">Name of the group to add an instance to</param>
        /// <param name="instanceName">The name of the instance to add</param>
        /// <returns>The counters for the specified instance</returns>
        /// <typeparam name="T">An enum type to use for identifying the counters</typeparam>
        internal static Dictionary<T, PerformanceCounter> AddInstance<T>(string categoryName, string instanceName)
            where T : struct
        {
            if (!PerformanceCounterManager.counters.ContainsKey(categoryName))
            {
                return null;
            }

            string processQualifiedInstanceName = PerformanceCounterManager.GetProcessQualifiedInstanceName(instanceName);
            Dictionary<int, PerformanceCounter> instanceCounters = new Dictionary<int, PerformanceCounter>();
            foreach (KeyValuePair<int, CounterCreationData> def in PerformanceCounterManager.categories[categoryName])
            {
                PerformanceCounter counter = new PerformanceCounter();
                counter.InstanceLifetime = PerformanceCounterInstanceLifetime.Process;
                counter.CategoryName = categoryName;
                counter.InstanceName = processQualifiedInstanceName;
                counter.CounterName = def.Value.CounterName;
                counter.ReadOnly = false;
                instanceCounters[def.Key] = counter;
            }

            PerformanceCounterManager.counters[categoryName][processQualifiedInstanceName] = instanceCounters;
            return PerformanceCounterManager.GetCounters<T>(categoryName, instanceName);
        }

        /// <summary>
        /// Retrieves the counters based on instance name
        /// </summary>
        /// <param name="categoryName">Name of the group to add an instance to</param>
        /// <param name="instanceName">The name of the counter instance</param>
        /// <returns>The corresponding counters</returns>
        /// <typeparam name="T">An enum type to use in identifying the counters</typeparam>
        internal static Dictionary<T, PerformanceCounter> GetCounters<T>(string categoryName, string instanceName)
            where T : struct
        {
            if (!PerformanceCounterManager.counters.ContainsKey(categoryName))
            {
                return null;
            }

            string processQualifiedInstanceName = PerformanceCounterManager.GetProcessQualifiedInstanceName(instanceName);
            Dictionary<T, PerformanceCounter> typedCounters = new Dictionary<T, PerformanceCounter>(PerformanceCounterManager.counters.Count);
            foreach (KeyValuePair<int, PerformanceCounter> counterPair in PerformanceCounterManager.counters[categoryName][processQualifiedInstanceName])
            {
                typedCounters[(T)(object)(counterPair.Key)] = counterPair.Value;
            }

            return typedCounters; 
        }

        /// <summary>
        /// Creates the counter category if not present.
        /// </summary>
        /// <param name="categoryName">The name of the category to set up</param>
        /// <param name="categoryDefinitions">The category definitions</param>
        /// <param name="installCounters">If true, the counters are recreated (requires admin permissions)</param>
        internal static void SetupCounters(string categoryName, Dictionary<int, CounterCreationData> categoryDefinitions, bool installCounters)
        {
            if (PerformanceCounterManager.categories.ContainsKey(categoryName))
            {
                return;
            }

            if (!installCounters)
            {
                try
                {
                    if (!PerformanceCounterCategory.Exists(categoryName))
                    {
                        return;
                    }
                }
                catch (UnauthorizedAccessException)
                {
                    return;
                }
            }

            PerformanceCounterManager.categories.Add(categoryName, categoryDefinitions);
            PerformanceCounterManager.counters[categoryName] = new Dictionary<string, Dictionary<int, PerformanceCounter>>();
            Dictionary<int, CounterCreationData> counterDefinitions = PerformanceCounterManager.categories[categoryName];

            if (installCounters)
            {
                try
                {
                    if (PerformanceCounterCategory.Exists(categoryName))
                    {
                        PerformanceCounterCategory.Delete(categoryName);
                    }

                    CounterCreationDataCollection collection = new CounterCreationDataCollection(counterDefinitions.Values.ToArray());
                    PerformanceCounterCategory.Create(categoryName, "Microsoft Robotics performance counters", PerformanceCounterCategoryType.MultiInstance, collection);
                    Console.WriteLine("\tPerformance counters {0} installed.", categoryName);
                }
                catch (SecurityException)
                {
                    CounterCreationFailed();
                }
                catch (UnauthorizedAccessException)
                {
                    CounterCreationFailed();
                }
            }
        }

        /// <summary>
        /// Failure to create the performance counter category (security restriction).
        /// </summary>
        private static void CounterCreationFailed()
        {
            Console.WriteLine("Performance counters could not be created. You need to run the application as an administrator at least once.");
            counters = null;
        }

        /// <summary>
        /// Appends the process ID to the instance name
        /// </summary>
        /// <param name="instanceName">The counter instance name</param>
        /// <returns>A unique instance name to be used when setting up the counters</returns>
        private static string GetProcessQualifiedInstanceName(string instanceName)
        {
            return string.Format("{0} ({1})", instanceName, processName);
        }
    }
}
